#Iterators
class ExampleIterator:
    def __init__(self):
        self.index = 0
        self.data = [1, 2, 3]
    def __iter__(self):
        return self
    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration()
        rslt = self.data[self.index]
        self.index += 1
        return rslt

i = ExampleIterator()
next(i)
next(i)
next(i)
next(i)

for i in ExampleIterator():
    print(i)


#Putting __next__ and __iter__ together
class ExampleIterator:
    def __init__(self, data):
        self.index = 0
        self.data = data
    def __iter__(self):
        return self
    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration()
        rslt = self.data[self.index]
        self.index += 1
        return rslt

class ExampleIterable:
    def __init__(self):
        self.data = [1, 2, 3]
    def __iter__(self):
        return ExampleIterator(self.data)

for i in ExampleIterable():
    print(i)

[i * 3 for i in ExampleIterable()]


#The alternative iterable protocol: __getitem__()
class AlternateIterable:
    def __init__(self):
        self.data = [1, 2, 3]
    def __getitem__(self, idx):
        return self.data[idx]

[i for i in AlternateIterable()]


#Extended iter() form
import datetime
i = iter(datetime.datetime.now, None)
next(i)
next(i)
next(i)


#Reading files with extended iter
with open("ending_file.txt", 'rt') as f:
    for line in iter(lambda: f.readline().strip(), "END"):
        print(line)